home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Compendium Deluxe 2
/
LSD and 17bit Compendium Deluxe - Volume II.iso
/
a
/
prog
/
asmsrc
/
thesource-7.lha
/
Source
/
Articles
/
Stereoscopic
/
Stereo.lha
/
stereo.c
< prev
Wrap
C/C++ Source or Header
|
1994-05-27
|
15KB
|
426 lines
/* Name : stereo.c
*
* Notes: No double buffer is done in this program, so an extra
* WaitTOF() is needed to prevent the display from displaying
* graphics before they are drawn, that is, it prevents the
* top half of the screen from "disappearing." :)
*
* $Log$
*/
/******************************************************************************/
#include <stdlib.h>
#include <string.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>
#include <clib/dos_protos.h>
/******************************************************************************/
/* This is the general 8:8 fixed point data type. With this data type, 1.0
* is represented by 0x0100. Because of this, multiplication and division will
* introduce a ``magnitude error''. For example, 1.0 * 1.0 = 1.0, but 0x100 *
* 0x100 = 0x10000. To correct this, the result of multiplication must be
* shifted to the right by 8 bits, and the result of a division must be
* shifted to the left by 8 bits. */
typedef WORD Fixed;
/* This is a 2D point and is usually considered to be in device coordinates. */
typedef struct
{
WORD x;
WORD y;
} Point2D;
/* This is a 3D point and is always in world/local coordinates. */
typedef struct
{
Fixed x;
Fixed y;
Fixed z;
} Point3D;
/* This structure models one surface (polygon). It consists
* of a pointer to the next polygon, the number of points for
* itself, and a list of points. This list is NOT actual points,
* but rather the indexes of the points in its objects point
* list. */
typedef struct
{
APTR * Next;
WORD NumPoints;
WORD * Points;
} Surface;
/* The object structure consists of two main parts. The first
* is the list of points. This consists of the number of points
* followed by a pointer to the array of points (local coords).
* The second part is a pointer to a linked list of surfaces.
* (See above). */
typedef struct
{
WORD NumPoints;
Point3D * Points;
Surface * Surfaces;
} Object3D;
/******************************************************************************/
void Rotate( Point3D * input, Point3D * World, Fixed XAngle, Fixed YAngle,
Fixed ZAngle, WORD NumPoints );
void Project( Point3D * world, Point2D * view,
Fixed XCent, Fixed YCent, Fixed ZCent, WORD NumPoints );
void Render( struct RastPort * rp, Object3D * obj, Point2D * points,
BOOL hsr, UBYTE Pen );
BOOL IsSurfaceVisible( Surface * surf, Point2D * points );
/******************************************************************************/
/* To generate sin() and cos(), I just use a 256+64 entry look-up
* table that contains the values of sine from 0 to 360+90 degrees. */
extern const Fixed SineTable[];
#define DEG_90 (64)
/******************************************************************************/
/* This file contains the defs' for the objects */
#include "objects.h"
/******************************************************************************/
#define LEFT_COLOR 1 /* Left eye is color01. */
#define RIGHT_COLOR 2 /* Right eye is color02. */
struct ColorSpec colors[] =
{
{ 0, 0, 0, 0 }, /* Background color. */
{ 1, 8, 0, 0 }, /* Left eye color. */
{ 2, 0, 0, 8 }, /* Right eye color. */
{ 3, 8, 0, 8 }, /* Left and right color.*/
{ -1, 0, 0, 0 } /* End of color list. */
};
/******************************************************************************/
/* This is the part that makes this type of routine difficult. The distnace *
* between a persons eyes varys from person to person. This is the number *
* that works for me. My eyes are about 328mm from the center of my nose. */
#define EYE_DISTANCE 50 /* From your nose to the middle of your eye. */
/******************************************************************************/
int main()
{
struct Screen * scr; /* Our screen */
struct RastPort * rp; /* RastPort to draw in */
Object3D * Obj; /* Ptr to our object */
/* These are my angles of rotation about the given axis. */
Fixed XAngle, YAngle, ZAngle;
Point3D * World; /* Array of rotated world coordinates. */
Point2D * View; /* Array of projected screen pts */
WORD frames; /* Number of frames remaining */
BOOL hsr; /* This turns hidden surfaces on/off */
/* Open the screen we want to work on. */
scr = OpenScreenTags( NULL, SA_Width, 320L,
SA_Height, 200L,
SA_Type, CUSTOMSCREEN,
SA_Depth, 2L,
SA_Colors, colors,
SA_DisplayID, LORES_KEY,
SA_ShowTitle, FALSE,
TAG_END );
if ( scr == NULL )
{
PutStr( "Could not open screen.\n" );
return( 5 );
}
/* Since I'm not double-buffering, I'll just use my screen's default
* RastPort to draw in. */
rp = &(scr->RastPort);
/* Allocate memory for the arrays of rotated and projected points. */
World = (Point3D *)AllocVec( sizeof( Point3D ) * Obj->NumPoints,
MEMF_ANY|MEMF_CLEAR );
View = (Point2D *)AllocVec( sizeof( Point2D ) * Obj->NumPoints,
MEMF_ANY|MEMF_CLEAR );
/* Initialize the angles of rotation. */
XAngle = YAngle = ZAngle = 0L;
Obj = &Cylinder;
hsr = FALSE;
/* For 600 frames (~10 seconds) we'll rotate and re-draw our happy
* vector object. */
for ( frames = 300 ; frames-- ; /* empty */ )
{
/* Wait until the end of the frame (i.e., the vertical blank) to
* draw the next frame. */
WaitTOF();
/* Clear the screen. */
Move( rp, 0, 0 );
ClearScreen( rp );
/* Rotate the object by the new angles */
Rotate( Obj->Points, World, XAngle, YAngle, ZAngle, Obj->NumPoints );
/* Project for the left eye. */
Project( World, View, EYE_DISTANCE, 0, 0, Obj->NumPoints );
Render( rp, Obj, View, hsr, LEFT_COLOR );
/* Project for the right eye. */
Project( World, View, -EYE_DISTANCE, 0, 0, Obj->NumPoints );
Render( rp, Obj, View, hsr, RIGHT_COLOR );
/* See the notes above as to why this is here. */
WaitTOF();
/* Get the new angles and make sure they are in range. */
XAngle += 1; YAngle += 2; ZAngle += 3;
XAngle &= 0xff; YAngle &= 0xff; ZAngle &= 0xff;
}
FreeVec( World ); FreeVec( View );
CloseScreen( scr );
return( 0 );
}
/******************************************************************************/
/* Name : Rotate()
*
* Notes: Transforms the given array of points by the specified angles.
*/
void Rotate( Point3D * input, Point3D * World, Fixed XAngle, Fixed YAngle,
Fixed ZAngle, WORD NumPoints )
{
Fixed matrix[3][3]; /* transform matrix */
/* These are my cos and sin values for the three angles */
Fixed sinx, cosx, siny, cosy, sinz, cosz;
Fixed temp; /* temp storage for sub exprs */
/* Get all the sine and cosine values. */
sinx = SineTable[ XAngle ]; cosx = SineTable[ XAngle + DEG_90 ];
siny = SineTable[ YAngle ]; cosy = SineTable[ YAngle + DEG_90 ];
sinz = SineTable[ ZAngle ]; cosz = SineTable[ ZAngle + DEG_90 ];
/* My rotation matrix looks like this:
*
cos(z)cos(y) sin(z)cos(x)+cos(z)sin(y)sin(x) sin(z)sin(x)-cos(z)sin(y)cos(x)
-sin(z)cos(y) cos(z)cos(x)-sin(z)sin(y)sin(x) cos(z)sin(x)+sin(z)sin(y)cos(x)
sin(y) -cos(y)sin(x) cos(y)sin(x)
*
* I chose this matrix because it is easy to code and still provides a
* reasonable modeling transform for this situation.
*/
/* Calculate column 1. */
matrix[0][0] = ( cosz * cosy) >> 8;
matrix[1][0] = (-sinz * cosy) >> 8;
matrix[2][0] = siny;
/* Calculate column 2. */
temp = ( sinx * siny ) >> 8;
matrix[0][1] = (sinz*cosx + cosz*temp) >> 8;
matrix[1][1] = (cosz*cosx - sinz*temp) >> 8;
matrix[2][1] = (-cosy*sinx) >> 8;
/* Calculate column 3. */
temp = (siny*cosx) >> 8;
matrix[0][2] = (sinz*sinx - cosz*temp) >> 8;
matrix[1][2] = (cosz*sinx + sinz*temp) >> 8;
matrix[2][2] = (cosy*cosx) >> 8;
/* Send each input point through the transformation
* matrix and store the result. */
for( /* empty */ ; NumPoints-- ; input++, World++ )
{
World->x = ( matrix[0][0] * input->x +
matrix[0][1] * input->y +
matrix[0][2] * input->z ) >> 8;
World->y = ( matrix[1][0] * input->x +
matrix[1][1] * input->y +
matrix[1][2] * input->z ) >> 8;
World->z = ( matrix[2][0] * input->x +
matrix[2][1] * input->y +
matrix[2][2] * input->z ) >> 8;
}
return;
}
/******************************************************************************/
/* Name : Project()
*
* Notes: This function projects an array of 3D points to a 2D array using
* either perspective or parallel projection.
*/
void Project( Point3D * world, Point2D * view,
Fixed XCent, Fixed YCent, Fixed ZCent,
WORD NumPoints )
{
while( NumPoints-- )
{
Fixed base; /* project by dividing by this. */
/* First we need to decide if the Z value of the point should
* have any bearing on the projection. A base of 1500 is used
* for two reasons:
* 1. Prevents divide by 0 and divide by negative numbers
* 2. Minimizes the difference between parallel and
* perspective projection.
*
* Then the object's Z value is added. This won't effect the
* projection, but will give an overall depth-cue by scaling
* the whole object. */
base = (world->z + 1500) + ZCent;
/* Project the point... */
view->x = ((LONG)(world->x + XCent) << 8) / base;
view->y = ((LONG)(world->y + YCent) << 8) / base;
/* ...and transform to screen coordinates. */
view->x += 160;
view->y += 100;
view++;
world++;
}
}
/******************************************************************************/
/* Name : Render()
*
* Notes: This function renders the given 3D object, using the specified
* 2D device coordinates to the given RastPort.
*/
void Render( struct RastPort * rp, Object3D * obj, Point2D * points,
BOOL hsr, UBYTE Pen )
{
WORD x,y; /* coords of the first point this poly */
Surface * surface; /* ptr to the current surface */
SetAPen( rp, Pen );
for ( surface = obj->Surfaces ; surface != NULL ; surface = surface->Next )
{
if ( !hsr || IsSurfaceVisible( surface, points ) )
{
WORD NumPoints; /* # of points this surface */
WORD lcv; /* current point for this polygon */
/* Fetch the number of points and the first point. */
NumPoints = surface->NumPoints;
x = points[ surface->Points[0] - 1 ].x;
y = points[ surface->Points[0] - 1 ].y;
/* Move to the first point to start drawing. */
Move( rp, x, y );
/* For each point in the polygon, draw a line. Note: the
* point indexes in the polygon structure are stored as
* base 1, whereas the point array is base 0. */
for( lcv = 1 ; lcv < NumPoints ; lcv++ )
{
Draw( rp, points[ surface->Points[lcv] - 1 ].x,
points[ surface->Points[lcv] - 1 ].y );
}
/* Connect back to the first point. */
Draw( rp, x, y );
}
}
return;
}
/******************************************************************************/
/* Name : IsSurfaceVisible()
*
* Notes: This function examines the give surface and determines if it
* faces toward the viewer. If it does, this function will return
* true, otherwise it will return false.
*
* The algorithm for this routine can be arrived at two different ways.
* The way that I use goes something like this:
*
* If three points are counter-clockwise, then the angle between the
* two rays they define will be less than 180 degrees. This means that
* given the following two lines:
*
* / A The slope of line BC will be less than the slope
* / of line AB. If DX1 is the delta X from A to B,
* / DY1 is the delta Y from A to B, DX2 is the delta
* / from B to C, and DY2 is the delta Y from B to C,
* B < then (DY1 / DX1) > (DY2 / DX2) must be true for a
* \ counter clockwise surface. Cross multiplication
* \ simplifies this to (DX2 * DY1) > (DX1 * DY2).
* \ C
*
* This same formula can be derived by reducing the cross product formula
* to only look at the Z values, but I'll leave that as an exercise for
* the reader. ;^)
*/
BOOL IsSurfaceVisible( Surface * surf, Point2D * points )
{
Fixed x1, y1, x2, y2, x3, y3; /* three points on the surface */
Fixed dx1, dx2, dy1, dy2; /* delta values */
/* Fetch the first three points of the surface. */
x1 = points[ surf->Points[0] - 1 ].x;
y1 = points[ surf->Points[0] - 1 ].y;
x2 = points[ surf->Points[1] - 1 ].x;
y2 = points[ surf->Points[1] - 1 ].y;
x3 = points[ surf->Points[2] - 1 ].x;
y3 = points[ surf->Points[2] - 1 ].y;
/* Calculate the deltas. */
dx1 = x1 - x2;
dx2 = x2 - x3;
dy1 = y1 - y2;
dy2 = y2 - y3;
/* Do the magic! */
return( (dx1 * dy2) < (dx2 * dy1) );
}